package lang

import (
	"database/sql/driver"
	"encoding/base64"
	"fmt"
	"gitee.com/kristas/booting-go/framework/common/util/cryptox"
	"github.com/stoewer/go-strcase"
	"regexp"
	"strconv"
	"strings"
)

type String struct {
	string
}

func NewString(v interface{}) *String {
	newString := NewStringValue(v)
	return &newString
}

func NewStringValue(v interface{}) String {
	var s = String{}
	s.Scan(v)
	return s
}

func NewEmptyString() *String {
	return &String{}
}

func (r String) String() string {
	return r.string
}

func (r *String) set(str string) *String {
	r.string = str
	return r
}

func (r *String) Concat(new ...string) *String {
	if len(new) == 0 {
		return r
	}
	var tmp string
	for i := 0; i < len(new); i++ {
		tmp += new[i]
	}
	return r.set(r.String() + tmp)
}

func (r *String) SubString(from, to int) *String {
	return NewString(r.String()[from:to])
}

func (r *String) SubStringFrom(from int) *String {
	return NewString(r.String()[from:])
}

func (r *String) SubStringTo(to int) *String {
	return NewString(r.String()[:to])
}

func (r *String) GetInt() (int, error) {
	return strconv.Atoi(r.String())
}

func (r *String) GetBytes() []byte {
	return []byte(r.String())
}

func (r *String) Hash() []byte {
	hash, _ := cryptox.Hash(r.String())
	return hash
}

func (r *String) HashString() string {
	return string(r.Hash())
}

func (r *String) EqualsHash(hash string) bool {
	ok, _ := cryptox.HashValidate(r.string, hash)
	return ok
}

func (r *String) Equals(other string) bool {
	return r.String() == other
}

func (r *String) Base64Encode() string {
	return base64.StdEncoding.EncodeToString(r.GetBytes())
}

func (r *String) Base64Decode(b64 string) *String {
	bytes, _ := base64.StdEncoding.DecodeString(b64)
	r.set(string(bytes))
	return r
}

func (r *String) EqualsIgnoreCase(other string) bool {
	return r.ToLower().Equals(strings.ToLower(other))
}

func (r *String) Split(sep string) Slice {
	return NewSlice(strings.Split(r.String(), sep))
}

func (r *String) SplitN(sep string, n int) Slice {
	return NewSlice(strings.SplitN(r.String(), sep, n))
}

func (r *String) ToLower() *String {
	return NewString(strings.ToLower(r.String()))
}

func (r *String) ToUpper() *String {
	return NewString(strings.ToUpper(r.String()))
}

func (r *String) FirstLetterUpper() *String {
	return NewString(strings.ToUpper(string(r.String()[0])) + r.String()[1:])
}

func (r *String) FirstLetterLower() *String {
	return NewString(strings.ToLower(string(r.String()[0])) + r.String()[1:])
}

func (r *String) Replace(old, new string, n int) *String {
	return NewString(strings.Replace(r.String(), old, new, n))
}

func (r *String) ReplaceAll(old, new string) *String {
	return NewString(strings.ReplaceAll(r.String(), old, new))
}

func (r *String) Contains(substr string) bool {
	return strings.Contains(r.String(), substr)
}

func (r *String) StartWith(substr string) bool {
	return strings.HasPrefix(r.String(), substr)
}

func (r *String) EndWith(substr string) bool {
	return strings.HasSuffix(r.String(), substr)
}

func (r *String) IndexOf(substr string) int {
	return strings.Index(r.String(), substr)
}

func (r *String) LastIndexOf(substr string) int {
	return strings.LastIndex(r.String(), substr)
}

func (r *String) Length() int {
	return len(r.String())
}

func (r *String) Trim(sep string) *String {
	return NewString(strings.Trim(r.String(), sep))
}

func (r *String) Trims() *String {
	if r.StartWith(" ") || r.EndWith(" ") {
		return r.Trim(" ").Trims()
	}
	if r.StartWith("\r") || r.EndWith("\r") {
		return r.Trim("\r").Trims()
	}
	if r.StartWith("\n") || r.EndWith("\n") {
		return r.Trim("\n").Trims()
	}
	return r
}

func (r *String) IsEmpty() bool {
	return r.String() == ""
}

func (r *String) Matches(pattern string) (bool, error) {
	return regexp.MatchString(pattern, r.String())
}

func (r *String) FindByPattern(pattern string) ([]string, error) {
	compile, err := regexp.Compile(pattern)
	if err != nil {
		return nil, err
	}
	return compile.FindAllString(r.String(), -1), nil
}

func (r *String) RemovePrefix(prefix string) *String {
	if strings.HasPrefix(r.String(), prefix) {
		return NewString(r.String()[len(prefix):])
	}
	return r
}

func (r *String) RemoveSuffix(suffix string) *String {
	if strings.HasSuffix(r.String(), suffix) {
		return NewString(r.String()[:len(r.String())-len(suffix)])
	}
	return r
}

func (r *String) Format(args ...interface{}) *String {
	return NewString(fmt.Sprintf(r.String(), args...))
}

// CamelCase
// example:
//
// hello_world -> HelloWorld
//
// hello-world -> HelloWorld
func (r *String) CamelCase() *String {
	return NewString(strcase.UpperCamelCase(r.String()))
}

// SnakeCase
// example:
//
// HelloWorld -> hello_world
//
// hello-world -> hello_world
func (r *String) SnakeCase() *String {
	return NewString(strcase.SnakeCase(r.String()))
}

// KebabCase
// example:
//
// hello_world -> hello-world
//
// HelloWorld -> hello-world
func (r *String) KebabCase() *String {
	return NewString(strcase.KebabCase(r.String()))
}

// marshaller

func (r String) MarshalBinary() (data []byte, err error) {
	return r.GetBytes(), nil
}

func (r *String) UnmarshalBinary(data []byte) error {
	r.set(string(data))
	return nil
}

func (r String) MarshalText() (text []byte, err error) {
	return r.MarshalBinary()
}

func (r *String) UnmarshalText(text []byte) error {
	return r.UnmarshalBinary(text)
}

func (r String) MarshalJSON() ([]byte, error) {
	return r.MarshalBinary()
}

func (r *String) UnmarshalJSON(bytes []byte) error {
	return r.UnmarshalBinary(bytes)
}

func (r *String) Scan(src interface{}) error {
	if src == nil {
		r.set("")
		return nil
	}
	switch src.(type) {
	case string:
		r.set(src.(string))
	case []byte:
		r.set(string(src.([]byte)))
	default:
		r.set(fmt.Sprintf("%v", src))
	}
	return nil
}

func (r *String) Value() (driver.Value, error) {
	return r.MarshalBinary()
}
